테스트 더블이란
JeongSeulho
2024년 01월 27일
준비중...
클립보드로 복사
0. 들어가며
테스트 더블에 대해 정리
1. 테스트 더블이란?
테스트를 위해 실제 구현을 대체하는 방법을 지칭하며 다양한 종류가 있음(모킹 등)
2. 테스트 더블 사용 이유
- 실제 구현체를 사용하지 못하는 경우, 대체할 수 있는 방법이 필요
- 테스트 코드와 외부 의존성을 분리하여 테스트 코드의 안정성을 높이기 위함
- 예외 처리에 대한 상황을 재현하여 테스트를 진행하기 위함
3. Dummy
테스트 환경에서 특정 함수 또는 모듈이 필요하지만, 해당 모듈의 구현이나 실행은 필요 없는 경우 사용
즉, 테스트 환경에서 호출만 가능하면 되는 경우 사용
아래는 React Hook Form
테스트를 위한 컴포넌트
const TestForm = props => {
const methods = useForm({
defaultValues: {
name: '',
address: '',
phone: '',
requests: '',
coupon: NO_COUPON_ID,
...props,
},
});
return (
<FormProvider {...methods}>
<ShippingInformationForm />
{/* 아래에서 handleSubmit에 아무것도 없는 빈 콜백 () => {}을 전달하고 있다(Dummy) */}
<button type="button" onClick={methods.handleSubmit(() => {})}>
테스트 버튼
</button>
</FormProvider>
);
};
4. Stub
모듈이 호출 시 정해진 값을 반환하도록 하는 방법이며 내부 로직을 구현하지는 않음 단, 아주 간단한 로직이 포함될 수 있음
고려된 케이스 이외의 경우는 대응 불가
export const handlers = [
...[
apiRoutes.users,
apiRoutes.product,
apiRoutes.categories,
apiRoutes.couponList,
].map(path =>
rest.get(`${API_DOMAIN}${path}`, (_, res, ctx) =>
res(ctx.status(200), ctx.json(response[path])),
),
),
rest.get(`${API_DOMAIN}${apiRoutes.products}`, (req, res, ctx) => {
const data = response[apiRoutes.products];
const offset = Number(req.url.searchParams.get('offset'));
const limit = Number(req.url.searchParams.get('limit'));
const products = data.products.filter(
(_, index) => index >= offset && index < offset + limit,
);
return res(
ctx.status(200),
// 아주 간단한 로직이 포함된 Stub
ctx.json({ products, lastPage: products.length < limit }),
);
}),
rest.get(`${API_DOMAIN}${apiRoutes.profile}`, (req, res, ctx) => {
return res(ctx.status(200), ctx.json(null));
}),
rest.post(`${API_DOMAIN}${apiRoutes.users}`, (req, res, ctx) => {
if (req.body.name === 'FAIL') {
return res(ctx.status(500));
}
return res(ctx.status(200));
}),
rest.post(`${API_DOMAIN}${apiRoutes.login}`, (req, res, ctx) => {
if (req.body.email === 'FAIL@gmail.com') {
return res(ctx.status(401));
}
return res(
ctx.status(200),
ctx.json({
access_token: 'access_token',
}),
);
}),
rest.post(`${API_DOMAIN}${apiRoutes.log}`, (_, res, ctx) => {
return res(ctx.status(200));
}),
];
5. Spy
모듈의 호출 정보까지 기록하는 방법
주로 호출 횟수나 인자를 검증하는데 사용
const navigateFn = vi.fn();
vi.mock('react-router-dom', async () => {
const original = await vi.importActual('react-router-dom');
return {
...original,
// 호출 정보를 기록하는 Spy
useNavigate: () => navigateFn,
// 특정 값을 반환하는 Stub
useLocation: () => ({
pathname: 'pathname',
}),
};
});
import Cookies from 'js-cookie';
describe('로그인이 성공한 경우', () => {
it('전달된 access_token을 쿠키에 저장하는 메서드를 호출한다', async () => {
const { user } = await render(<Forms />);
const submitButton = screen.getByRole('button');
// 함수 구현을 그대로 사용하면서 호출 정보를 기록기 위한 spyOn
vi.spyOn(Cookies, 'set');
await user.type(screen.getByLabelText('이메일'), 'email@gmail.com');
await user.type(screen.getByLabelText('비밀번호'), 'password123');
await user.click(submitButton);
expect(Cookies.set).toHaveBeenCalled(1);
expect(Cookies.set).toHaveBeenCalledWith('access_token', 'access_token');
});
});
6. Mock
실제 모듈과 유사하게 행동하도록 만들어진 모의 객체
검증시 행동을 기반으로 검증
const navigateFn = vi.fn();
// react-router-dom 모듈을 Mock
vi.mock('react-router-dom', async () => {
const original = await vi.importActual('react-router-dom');
return {
...original,
// 호출 정보를 기록하는 Spy
useNavigate: () => navigateFn,
// 특정 값을 반환하는 Stub
useLocation: () => ({
pathname: 'pathname',
}),
};
});
프론트엔드에서는 스텁만 사용하여 상태를 검증하는 경우는 거의 없음 모의 객체 혹은 실제 모듈의 구현에 Stub이나 Spy를 주입하여 행동을 검증
7. Fake
테스트 전용으로 만들어진 단순한 모듈 또는 객체
const cart = {
6: {
id: 6,
title: 'Handmade Cotton Fish',
price: 100,
description:
'The slim & simple Maple Gaming Keyboard from Dev Byte comes with a sleek body and 7- Color RGB LED Back-lighting for smart functionality',
images: [
'https://user-images.githubusercontent.com/35371660/230712070-afa23da8-1bda-4cc4-9a59-50a263ee629f.png',
'https://user-images.githubusercontent.com/35371660/230711992-01a1a621-cb3d-44a7-b499-20e8d0e1a4bc.png',
'https://user-images.githubusercontent.com/35371660/230712056-2c468ef4-45c9-4bad-b379-a9a19d9b79a9.png',
],
count: 3,
},
7: {
id: 7,
title: 'Awesome Concrete Shirt',
price: 50,
description:
'The Nagasaki Lander is the trademarked name of several series of Nagasaki sport bikes, that started with the 1984 ABC800J',
images: [
'https://user-images.githubusercontent.com/35371660/230762100-b119d836-3c5b-4980-9846-b7d32ea4a08f.png',
'https://user-images.githubusercontent.com/35371660/230762118-46d965ab-7ea8-4e8a-9c0f-3ed90f96e1cd.png',
'https://user-images.githubusercontent.com/35371660/230762139-002578da-092d-4f34-8cae-2cf3b0dfabe9.png',
],
count: 4,
},
};
mockUseCartStore({ cart });